/**
 * @author: ATroshanin
 * Date: 09.04.2010
 * Time: 14:34:33
 */
package ru.papergames.battleserver.model.battle;

import javolution.util.FastMap;
import org.apache.log4j.Logger;
import ru.papergames.battleserver.model.PaperGameFlashUser;
import ru.papergames.battleserver.model.PaperGameNpcUser;
import ru.papergames.battleserver.model.battle.map.BattleMap;
import ru.papergames.battleserver.model.battle.npc.NpcsManager;
import ru.papergames.battleserver.model.battle.turnmanager.TurnManager;
import ru.papergames.battleserver.network.WebServerClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

// TODO: we need to remove somehow battles that are finished from allBattles map.

public class BattleManager {

    private static final Logger log = Logger.getLogger(BattleManager.class.getName());
    private static final int OBSTACLE_AREA_WIDTH = 5;
    private static final int OBSTACLE_AREA_HEIGHT = 5;
    private static final int OBSTACLE_SQUARE = OBSTACLE_AREA_WIDTH * OBSTACLE_AREA_HEIGHT;

    private static BattleManager instance = new BattleManager();

    // thread safe map for battles.
    // Actually only web-server(tomcat app) will initiate creation of new battles.
    // And removing battles will be done via this BattleManager
    private FastMap<Integer, AbstractBattle> allBattles = new FastMap<Integer, AbstractBattle>().shared();

    //private ThreadController threadController;

    public static BattleManager getInstance() {
        return instance;
    }

    private BattleManager() {
        //threadController = new ThreadController(this);
    }

    public AbstractBattle createNewBattle(WebServerClient client, int battleOrderId, int teamCount,
                                List<PaperGameFlashUser> pcUsers,
                                List<PaperGameNpcUser> npcUsers) {
        return createNewBattle(client, battleOrderId, teamCount, pcUsers, npcUsers, false);
    }

    public AbstractBattle createNewBattle(WebServerClient wsClient, int battleOrderId, int teamsCount,
                                List<PaperGameFlashUser> pcUsers,
                                List<PaperGameNpcUser> npcUsers,
                                boolean fakeBattle) {
        if (teamsCount < 2 || teamsCount > 10) {
            log.error("BAD teams count: " + teamsCount + " returning");
            return null;
        }
        AbstractBattle battle = new BasicBattle(
                battleOrderId,
                wsClient,
                pcUsers.size(),
                (npcUsers == null ? 0 : npcUsers.size()),
                teamsCount,
                new BattleMap(20, 15));
        for (PaperGameFlashUser pcUser : pcUsers) {
            BattlePlayer player = new BattlePlayer(battle, pcUser, pcUser.getTeamId());
            battle.addBattleMember(player);
            pcUser.setBattle(battle);
            pcUser.setBattleMember(player);
            pcUser.setInBattleState();
        }
        if (npcUsers != null) {
            for (PaperGameNpcUser npcUser : npcUsers) {
                BattlePlayer player = new BattlePlayer(battle, npcUser, npcUser.getTeamId());
                battle.addBattleMember(player);
                npcUser.setBattle(battle);
                npcUser.setBattleMember(player);

                NpcsManager.getInstance().addNpcToProcess(player);
            }
        }
        log.info("adding new battle. ID: " + battle.getBattleOrderId());
        allBattles.put(battle.getBattleOrderId(), battle);

        battle.arrangeTurnsAndSpawnSoldiers();
        battle.sendBattleInfoToMembers();

        addBattleInTurnProcess(battle);

        return battle;
    }

    public AbstractBattle getBattle(int battleId) {
        return allBattles.get(battleId);
    }

    private void addBattleInTurnProcess(AbstractBattle battle) {
        // TODO: Think about moving this code in separate thread from threadpoolmanager.
        boolean res = TurnManager.getInstance().addBattleInTurnProcess(battle);
        if (!res) {
            // If we failed to add battle to turn manager.
            // Then it means that server is full and ew need to cancel this battle.
            allBattles.remove(battle.getBattleOrderId());
            // TODO: send some notifications about this event to users...
        }
    }

    public void removeBattle(AbstractBattle battle) {
        if (battle != null) {
            log.info("Removing battle: " + battle.getBattleOrderId());
//            log.info("test stack trace:");
//            log.info("__________________________________________");
//            Throwable t = new Throwable();
//            t.printStackTrace();
//            log.info("__________________________________________");
            allBattles.remove(battle.getBattleOrderId());
            battle.notifyWebServerAboutBattleFinish();
        }
    }

    /*public void removeBattlesByTimeout() {
        FastList<AbstractBattle> battles = new FastList<AbstractBattle>();
        for (FastMap.Entry<Integer, AbstractBattle> e = allBattles.head(), end = allBattles.tail(); (e = e.getNext()) != end;) {
            AbstractBattle battle = e.getValue();
            if (battle.isBattleIniting() && battle.isInitingTimeout()) {
                battles.add(battle);
            }
        }
        for (AbstractBattle battle : battles) {
            if (battle.isBattleIniting()) {
                boolean removeBattle = false;
                boolean startBattle = false;
                synchronized (battle) {
                    if (battle.isBattleIniting()) {
                        for (BattlePlayer member : battle.getBattlePcMembers()) {
                            if(member.isConnected()) {
                                startBattle = true;
                                break;
                            }
                        }
                        if (!startBattle) {
                            battle.setBattleState(BattleState.FINISHED);
                            removeBattle = true;
                        }
                    }
                }
                if (startBattle) {
                    System.out.println("BattleManager.removeBattlesByTimeout");
                    battle.setBattleState(BattleState.STARTING);
                    addBattleInTurnProcess(battle);
                }
                if (removeBattle) {
                    removeBattle(battle);
                }
            }

        }
    }*/

    public List<AbstractBattle> getBattles() {
        int lstSize = (int) (allBattles.size() * 1.2);
        List<AbstractBattle> battles = new ArrayList<AbstractBattle>(lstSize);
        for (Map.Entry<Integer, AbstractBattle> battleEntry : allBattles.entrySet()) {
            battles.add(battleEntry.getValue());
        }
        return battles;
    }

    public void start() {
        //threadController.start();
    }

    public void shutDown() {
        //threadController.shutDown();
    }

    /*private class ThreadController extends Thread{

        private BattleManager battleManager;
        private volatile boolean shutdown;

        private ThreadController(BattleManager battleManager) {
            this.battleManager = battleManager;
        }

        public void run () {
            while (!shutdown) {
                try {
                    battleManager.removeBattlesByTimeout();
                } catch (Exception e) {
                    e.printStackTrace();
                    log.warn(e.getMessage());
                }
                try {
                    Thread.sleep(6 * 1000);
                } catch (InterruptedException e) {
                    //
                    log.info("BattleManager$ThreadController was interrupted while sleep");
                }
            }
        }

        public void shutDown() {
            shutdown = true;
            this.interrupt();
        }
    }*/

}
